//
//  EXObjectStore.m
//  ORM
//
//  Created by PH on 21.12.09.
//  Copyright 2009-2010 Codesign. All rights reserved.
//

#import "EXObjectStore.h"
#import "ORMTCKeyValueStore.h"
#import "EXObjectProxy.h"
#import "EXQueryOptimizer.h"
#import "EXMappableObject.h"
#import "EXiPhoneAdditions.h"
#import "EXNSAdditions.h"
#import <objc/runtime.h>

#define kCacheCountLimit	100000

static NSMutableSet* allClasses;
static NSMutableDictionary* subclassMap;
static NSMutableDictionary* queryBlocks;

@implementation EXObjectStore

@synthesize currentTransaction;
@synthesize KVStore;
@synthesize mapOfIDs;
@synthesize cache;
@synthesize delegate;

+ (void)initialize {
	allClasses = [[NSMutableSet alloc] init];
	subclassMap = [[NSMutableDictionary alloc] init];
	queryBlocks = [[NSMutableDictionary alloc] init];
}

- (id)initWithPath:(NSString*)path {
	return [self initWithPath: path autosync: NO];
}

- (id)initWithPath:(NSString*)path autosync:(BOOL)_autosync {
	if ((self = [super init])) {
		KVStore = [[ORMTCKeyValueStore alloc] initWithPath: path autosync: _autosync];
		if ([KVStore isReady] == NO) [NSException raise: @"NSException" format: @"Cannot open database"];
		mapOfIDs = [[NSMutableDictionary alloc] init];
		cache = [[NSMutableDictionary alloc] init];
		//zombies = [[NSMutableArray alloc] init];
	}
	return self;
}

- (BOOL)createIndexOnField:(NSString*)fieldName {
	return [self createIndexOnField: fieldName rebuild: NO];
}

- (BOOL)createIndexOnField:(NSString*)fieldName rebuild:(BOOL)rebuild {
	return [self.KVStore createIndexOnField: [NSString stringWithFormat: @"field_%@", fieldName] rebuild: rebuild];
}

- (void)bindObject:(NSObject<EXMappableObject>*)object toObjectID:(NSString*)objectID {
	NSValue* pointerKey = [NSValue valueWithPointer: object];
	[self.mapOfIDs setObject: objectID forKey: pointerKey];
	[self.cache setObject: pointerKey forKey: objectID];
	[object __setObjectStore: self];
}

- (NSString*)IDOfObject:(NSObject<EXMappableObject>*)object {
	NSValue* pointerKey = [NSValue valueWithPointer: object];
	return [self.mapOfIDs objectForKey: pointerKey];
}

- (void)discardObject:(NSObject<EXAbstractMappableObject>*)object {
	//NSAssert(self.currentTransaction == nil, @"Transaction in progress"); // we don't care, there can be a transaction
	//NSLog(@"discarding");
	//EXTransaction* transaction = [self beginTransaction]; // collections are not thread safe so let's use a transaction
	//@try {
		NSValue* pointerKey = [NSValue valueWithPointer: object];
		//NSAssert([self.mapOfIDs objectForKey: pointerKey] != nil, @"Object missing in map of IDs");
		NSString* objectID = [self.mapOfIDs objectForKey: pointerKey];
		if (objectID != nil) {
			[self.mapOfIDs removeObjectForKey: pointerKey];
			//NSString* msg = [NSString stringWithFormat: @"Object missing in cache: %@ %@", objectID, object];
			//NSAssert([self.cache objectForKey: objectID] != nil, @"Object missing in cache");
			//NSLog(@"Discarding: %@", [object className]);
			[self.cache removeObjectForKey: objectID];
		}
	//} @finally {
	//	[transaction rollback];
	//}
	/*@synchronized (zombies) {
		[zombies addObject: _pointerKey];
		if (currentTransaction != nil) {
			EXTransaction* transaction = [self beginTransaction]; // collections are not thread safe so let's use a transaction
			@try {
				for (NSValue* pointerKey in zombies) {
					NSLog(@"discarding");
					//NSAssert([mapOfIDs objectForKey: pointerKey] != nil, @"Object missing in map of IDs");
					NSString* objectID = [mapOfIDs objectForKey: pointerKey];
					if (objectID != nil) {
						[mapOfIDs removeObjectForKey: pointerKey];
						NSAssert([cache objectForKey: objectID] != nil, @"Object missing in cache");
						[cache removeObjectForKey: objectID];
					}
				}
				[zombies removeAllObjects];
			} @finally {
				[transaction rollback];
			}
		}
	}*/
}

- (void)discardObjectGC:(NSObject<EXAbstractMappableObject>*)object {
	NSValue* pointerKey = [NSValue valueWithPointer: object];
	//NSAssert([self.mapOfIDs objectForKey: pointerKey] != nil, @"Object missing in map of IDs");
	NSString* objectID = [self.mapOfIDs objectForKey: pointerKey];
	if (objectID != nil) {
		[self.mapOfIDs removeObjectForKey: pointerKey];
		NSAssert([self.cache objectForKey: objectID] != nil, @"Object missing in cache");
		[self.cache removeObjectForKey: objectID];
	}
}

- (BOOL)removeObject:(NSObject<EXMappableObject>*)object {
	return [self removeObject: object createTransaction: YES];
}

- (BOOL)removeObject:(NSObject<EXMappableObject>*)object createTransaction:(BOOL)createTransaction {
	if ([object respondsToSelector: @selector(__object)]) {
		object = [(EXObjectProxy*) object __object]; // the object is a proxy
	}
	NSValue* pointerKey = [NSValue valueWithPointer: object];
	NSString* objectID = [self.mapOfIDs objectForKey: pointerKey];
	if (objectID == nil) return NO;
	if (createTransaction == YES && [self.KVStore beginTransaction] == NO) return NO;
	@try {
		[self.KVStore removeMapForKey: objectID];
		if (createTransaction == YES) [self.KVStore commitTransaction];
		return YES;
	} @catch (NSException* exception) {
		if (createTransaction == YES) [self.KVStore rollbackTransaction];
		[exception raise];
	}
	return NO;
}

- (BOOL)storeObject:(NSObject<EXMappableObject>*)object {
	//NSAssert(self.currentTransaction == nil, @"Current transaction isn't nil");
	NSMutableSet* processedObjects = [[NSMutableSet alloc] init];
	BOOL success = [self storeObject: object createTransaction: YES processedObjects: processedObjects];
	[processedObjects release];
	return success;
}

- (BOOL)storeObject:(NSObject<EXMappableObject>*)object createTransaction:(BOOL)createTransaction processedObjects:(NSMutableSet*)processedObjects {
	if ([object respondsToSelector: @selector(__object)]) {
		NSValue* pointerKey = [NSValue valueWithPointer: object];
		[processedObjects addObject: pointerKey];
		object = [(EXObjectProxy*) object __object]; // the object is a proxy
	}
	if (createTransaction == YES && [self.KVStore beginTransaction] == NO) return NO;
	@try {
		NSValue* pointerKey = [NSValue valueWithPointer: object];
		NSString* objectID = [self.mapOfIDs objectForKey: pointerKey];
		if ([processedObjects containsObject: pointerKey]) {
			NSAssert(createTransaction == NO, @"No transaction in progress");
			NSAssert(objectID != nil, @"Object in processed objects has not an UID");
			return YES;
		}
		//NSAssert([processedObjects containsObject: pointerKey] == NO, @"Object already in processed objects: %@ (%@)", [object className], objectID);
		[processedObjects addObject: pointerKey];
		NSString* className = [[object class] ORMTableName];
		if (objectID == nil) {
			objectID = [self.KVStore uniqueKey];
			[self.mapOfIDs setObject: objectID forKey: pointerKey];
			NSAssert([self.cache objectForKey: objectID] == nil, @"Object already in cache");
		}
		[self.cache setObject: pointerKey forKey: objectID];
		[object __setObjectStore: self];
		NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"object", @"type", className, @"class", nil];
		for (NSString* propertyName in [[object class] ORMPersistentFieldNames]) {
			if ([[[object class] ORMTransientFieldNames] containsObject: propertyName]) continue;
			id propertyValue = [object valueForKey: propertyName]; NSString* propertyType = NSStringFromClass([propertyValue class]);
			if (propertyValue == nil) {
				propertyType = @"nil";
				propertyValue = @"nil";
			} else if ([propertyValue isKindOfClass: [NSString class]]) {
				propertyType = @"string";
			} else if ([propertyValue isKindOfClass: [NSNumber class]]) {
				propertyType = @"number";
				propertyValue = [propertyValue stringValue];
			} else if ([propertyValue isKindOfClass: [NSDate class]]) {
				propertyType = @"date";
				NSNumber* num = [NSNumber numberWithDouble: [propertyValue timeIntervalSinceReferenceDate]];
				propertyValue = [num stringValue];
			} else if ([propertyValue isKindOfClass: [NSArray class]] || [propertyValue isKindOfClass: [NSSet class]]) {
				if ([propertyValue isKindOfClass: [NSArray class]]) propertyType = @"array";
				if ([propertyValue isKindOfClass: [NSSet class]]) propertyType = @"set";
				NSMutableArray* newArray = [[NSMutableArray alloc] initWithCapacity: [propertyValue count]];
				for (id el in propertyValue) {
					if ([el conformsToProtocol: @protocol(EXMappableObject)] == NO){
						[newArray addObject: el];
					} else {
						[newArray addObject: [self encodeMappableObject: el processedObjects: processedObjects]];
					}
				}
				NSMutableData* data = [[NSMutableData alloc] init];
				NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData: data];
				[archiver encodeObject: newArray forKey: @"collection"];
				[archiver finishEncoding];
				propertyValue = [data autorelease];
				[archiver release];
				[newArray release];
			} else if ([propertyValue isKindOfClass: [NSDictionary class]]) {
				propertyType = @"dict";
				NSMutableDictionary* newDict = [[NSMutableDictionary alloc] initWithCapacity: [propertyValue count]];
				for (id key in [propertyValue allKeys]) {
					id el = [propertyValue objectForKey: key];
					if ([el conformsToProtocol: @protocol(EXMappableObject)] == NO){
						[newDict setObject: el forKey: key];
					} else {
						[newDict setObject: [self encodeMappableObject: el processedObjects: processedObjects] forKey: key];
					}
				}
				NSMutableData* data = [[NSMutableData alloc] init];
				NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData: data];
				[archiver encodeObject: newDict forKey: @"collection"];
				[archiver finishEncoding];
				propertyValue = [data autorelease];
				[archiver release];
				[newDict release];
			} else if ([propertyValue conformsToProtocol: @protocol(EXMappableObject)]) {
				id childObject = propertyValue;
				if ([propertyValue respondsToSelector: @selector(__object)]) {
					if ([propertyValue __isActivated] == NO) {
						EXObjectReferenceInCollection* objectRef = [[EXObjectReferenceInCollection alloc] _initWithObjectID: [propertyValue __objectID]];
						propertyValue = [objectRef dataRepresentation];
						[objectRef release];
						childObject = nil;
					} else childObject = [propertyValue __object];
				}
				if (childObject != nil) {
					NSString* childObjectID = [self.mapOfIDs objectForKey: [NSValue valueWithPointer: childObject]];
					if (childObjectID == nil) { //[processedObjects containsObject: childObject] == NO) {
						NSAssert([processedObjects containsObject: childObject] == NO, @"Object not in map of IDs but in processed objects"); //: %@", [childObject className]);
						[self storeObject: childObject createTransaction: NO processedObjects: processedObjects];
						childObjectID = [self.mapOfIDs objectForKey: [NSValue valueWithPointer: childObject]];
					}
					EXObjectReferenceInCollection* objectRef = [[EXObjectReferenceInCollection alloc] _initWithObjectID: childObjectID];
					propertyValue = [objectRef dataRepresentation];
					[objectRef release];
				}
			} else if ([propertyValue conformsToProtocol: @protocol(NSCoding)]) {
				NSMutableData* data = [[NSMutableData alloc] init];
				NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData: data];
				[archiver encodeObject: propertyValue forKey: @"childObject"];
				[archiver finishEncoding];
				propertyValue = [data autorelease];
				[archiver release];
				propertyType = @"*";
			} else {
				propertyValue = nil;
				NSLog(@"Don't know how to store property '%@' of type %@", propertyName, propertyType);
			}
			if (propertyValue != nil) {
				[dict setObject: propertyType forKey: [NSString stringWithFormat: @"type_%@", propertyName]];
				[dict setObject: propertyValue forKey: [NSString stringWithFormat: @"field_%@", propertyName]];
			}
		}
		[self.KVStore putMap: dict forKey: objectID];
		if (createTransaction == YES) [self.KVStore commitTransaction];
		return YES;
		/*BOOL success = [KVStore putMap: [NSDictionary dictionaryWithObjectsAndKeys: @"object", @"type", className, @"class", nil]
								forKey: objectID];
		if (success == YES) {
			if ([KVStore removeRecordsMatchingCriteria: [NSDictionary dictionaryWithObjectsAndKeys: @"property", @"type", objectID, @"objectID", nil]]) {
				for (NSString* propertyName in [[object class] ORMPersistentFieldNames]) {
					id propertyValue = [object valueForKey: propertyName];
					NSString* propertyType = [propertyValue isKindOfClass: [NSString class]] ? @"NSString" : [propertyValue className];
					//NSLog(@"%@ %@ %@ %@ %@", className, propertyName, propertyType, propertyValue, objectID);
					success = [KVStore putMap: [NSDictionary dictionaryWithObjectsAndKeys: @"property", @"type", objectID, @"objectID", propertyName, @"name", propertyValue, @"value", propertyType, @"ptype", className, @"class", nil]
									   forKey: [KVStore uniqueKey]];
					if (success == NO) {
						NSLog(@"Cannot save property");
						if (createTransaction == YES) [KVStore rollbackTransaction];
						return NO;
					}
				}
				if (createTransaction == YES) [KVStore commitTransaction];
				return YES;
			} else {
				if (createTransaction == YES) [KVStore rollbackTransaction];
				return NO;
			}
		} else {
			if (createTransaction == YES) [KVStore rollbackTransaction];
			return NO;
		}*/
	} @catch (NSException* exception) {
		if (createTransaction == YES) [self.KVStore rollbackTransaction];
		[exception raise];
	}
	return NO;
}

- (EXObjectReferenceInCollection*)encodeMappableObject:(id)object processedObjects:(NSMutableSet*)processedObjects {
	id childObject = object;
	if ([object respondsToSelector: @selector(__object)]) {
		if ([object __isActivated] == NO) {
			return [[[EXObjectReferenceInCollection alloc] _initWithObjectID: [object __objectID]] autorelease];
			childObject = nil;
		} else childObject = [object __object];
	}
	if ([processedObjects containsObject: childObject] == NO) {
		[self storeObject: childObject createTransaction: NO processedObjects: processedObjects];
	}
	NSString* childObjectID = [self.mapOfIDs objectForKey: [NSValue valueWithPointer: childObject]];
	return [[[EXObjectReferenceInCollection alloc] _initWithObjectID: childObjectID] autorelease];
}

- (NSArray*)proxiesOfClass:(Class)cls {
	//NSAssert(self.currentTransaction == nil, @"Current transaction isn't nil");
	NSMutableArray* results = [NSMutableArray array];
	NSMutableDictionary* proxies = [[NSMutableDictionary alloc] init];
	[self proxiesOfClass: cls createTransaction: YES includeSubclasses: YES results: results proxies: proxies condBlock: nil];
	[proxies release];
	return results;
}

- (NSArray*)proxiesWithPredicate:(EXPredicate*)predicate {
	//NSAssert(self.currentTransaction == nil, @"Current transaction isn't nil");
	if (predicate.filterBlock != nil) {
		NSArray* results = [self proxiesOfClass: [predicate predicateClass]];
		NSMutableArray* filteredResults = [NSMutableArray array];
		for (id object in results) {
			if (predicate.filterBlock(object) == YES) [filteredResults addObject: object];
		}
		return filteredResults;
	} else {
		NSMutableArray* results = [NSMutableArray array];
		NSMutableDictionary* proxies = [[NSMutableDictionary alloc] init];
		[self proxiesOfClass: [predicate predicateClass] createTransaction: YES includeSubclasses: YES results: results proxies: proxies condBlock: [predicate condBlock]];
		[proxies release];
		return results;
	}
}

- (void)proxiesOfClass:(Class)cls createTransaction:(BOOL)createTransaction includeSubclasses:(BOOL)includeSubclasses results:(NSMutableArray*)results proxies:(NSMutableDictionary*)proxies condBlock:(void(^)(void*))condBlock {
	if (createTransaction == YES && [self.KVStore beginTransaction] == NO) return;
	@try {
		NSString* className = NSStringFromClass(cls);
		NSArray* metadata = [self.KVStore getMapsWithCriteria: [NSDictionary dictionaryWithObjectsAndKeys: @"object", @"type", className, @"class", nil]
											   condBlock: condBlock];
		for (NSDictionary* dict in metadata) {
			NSString* objectID = [dict objectForKey: @"__key"];
			EXObjectProxy* proxy = [proxies objectForKey: objectID];
			if (proxy == nil) {
				proxy = [[[EXObjectProxy alloc] initWithObjectID: objectID objectStore: self] autorelease];
				[proxies setObject: proxy forKey: objectID];
				//[self.mapOfIDs setObject: objectID forKey: [NSValue valueWithPointer: proxy]];
			}
			[results addObject: proxy];
		}
		if (includeSubclasses == YES) {
			NSSet* subclasses = [EXObjectStore subclassesOfClass: cls];
			//NSLog(@"subclasses: %@", subclasses);
			for (NSString* subclassName in subclasses) {
				Class subclass = NSClassFromString(subclassName);
				[self proxiesOfClass: subclass createTransaction: NO includeSubclasses: NO results: results proxies: proxies condBlock: condBlock];
			}
		}
		if (createTransaction == YES) [self.KVStore commitTransaction];
	} @catch (NSException* exception) {
		if (createTransaction == YES) [self.KVStore rollbackTransaction];
		[exception raise];
	}
}

- (id)objectWithObjectID:(NSString*)objectID {
	return [self objectWithObjectID: objectID lazyCollections: NO];
}

- (NSObject<EXMappableObject>*)objectWithObjectID:(NSString*)objectID lazyCollections:(BOOL)lazyCollections {
	//NSAssert(self.currentTransaction == nil, @"Current transaction isn't nil");
	NSMutableDictionary* processedObjects = self.currentTransaction != nil ? [self.currentTransaction.processedObjectsForReading retain] : [[NSMutableDictionary alloc] init];
	NSAssert(processedObjects != nil, @"Processed objects is nil");
	NSObject* object = [self objectWithObjectID: objectID
								lazyCollections: lazyCollections
							  createTransaction: self.currentTransaction == nil
							   processedObjects: processedObjects];
	[processedObjects release];
	return object;
}

- (NSObject<EXMappableObject>*)objectWithObjectID:(NSString*)objectID lazyCollections:(BOOL)lazyCollections createTransaction:(BOOL)createTransaction processedObjects:(NSMutableDictionary*)processedObjects {
	//NSAssert(objectID != nil, @"Object ID is nil");
	NSObject<EXMappableObject>* object = [[self.cache objectForKey: objectID] pointerValue];
	if (object == nil) object = [processedObjects objectForKey: objectID];
	if (object != nil) return object;
	if (createTransaction == YES && [self.KVStore beginTransaction] == NO) return nil;
	@try {
		NSDictionary* dict = [self.KVStore getMapForKey: objectID];
		if (dict == nil) { // Object with the specified ID not found
			//NSLog(@"Object with the specified ID '%@' not found", objectID);
			if (createTransaction == YES) [self.KVStore rollbackTransaction];
			return nil;
		}
		NSAssert([dict objectForKey: @"class"] != nil, @"UTF-8 string is NULL"); //: %@", dict);
		NSData* _data = [dict objectForKey: @"class"];
		NSString* className = [[[NSString alloc] initWithBytes: [_data bytes] length: [_data length] encoding: NSUTF8StringEncoding] autorelease];
		//NSString* className = [NSString stringWithUTF8String: [_data bytes]];
		Class cls = NSClassFromString(className);
		if (cls == Nil) { NSLog(@"Cannot get class for '%@'", className); return nil; }
		object = [[[cls alloc] init] autorelease];
		[object __setObjectStore: self];
		NSAssert([processedObjects objectForKey: objectID] == nil, @"Object already in processed objects");
		//if (object == nil) NSLog(@"oops: %@", className);
		NSAssert(object != nil, @"Object is nil");
		[processedObjects setObject: object forKey: objectID];
		NSAssert([self.cache objectForKey: objectID] == nil, @"Object already in cache");
		NSValue* pointerKey = [NSValue valueWithPointer: object];
		NSAssert(pointerKey != nil, @"pointerKey is nil");
		[self.cache setObject: pointerKey forKey: objectID];
		NSAssert([[self.mapOfIDs allValues] containsObject: objectID] == NO, @"Object already in map of IDs");
		NSAssert(objectID != nil, @"ObjectID is nil");
		[self.mapOfIDs setObject: objectID forKey: pointerKey];
		NSArray* fieldNames = [[object class] ORMPersistentFieldNames];
		/*for (NSString* attr in dict) {
			if ([attr rangeOfString: @"field_"].location == 0) {
				NSString* propertyName = [attr substringFromIndex: 6];
				NSString* propertyType = [NSString stringWithUTF8String: [[dict objectForKey: [NSString stringWithFormat: @"type_%@", propertyName]] bytes]];
				id propertyValue = [self decodePropertyOfType: propertyType data: [dict objectForKey: attr] lazyCollections: lazyCollections processedObjects: processedObjects];
				if ([fieldNames containsObject: propertyName]) [object setValue: propertyValue forKey: propertyName];
			}
		} /*/
		for (NSString* propertyName in fieldNames) {
			if ([[[object class] ORMTransientFieldNames] containsObject: propertyName]) continue;
			NSString* attr = [NSString stringWithFormat: @"field_%@", propertyName];
			NSData* data = [dict objectForKey: attr];
			if (data != nil) {
				NSData* _data = [dict objectForKey: [NSString stringWithFormat: @"type_%@", propertyName]];
				NSAssert(_data != nil, @"UTF-8 string is NULL");
				NSString* propertyType = [[NSString alloc] initWithBytes: [_data bytes] length: [_data length] encoding: NSUTF8StringEncoding];
				//NSString* propertyType = [NSString stringWithUTF8String: [_data bytes]];
				id propertyValue = [self decodePropertyOfType: propertyType data: data lazyCollections: lazyCollections processedObjects: processedObjects];
				[propertyType release]; //
				[object setValue: propertyValue forKey: propertyName];
			}
		} /**/
		if ([object respondsToSelector: @selector(awakeFromStore:)]) [object performSelector: @selector(awakeFromStore:) withObject: self];
		/*NSArray* properties = [KVStore getMapsWithCriteria: [NSDictionary dictionaryWithObjectsAndKeys: @"property", @"type", objectID, @"objectID", nil]];
		for (NSDictionary* property in properties) {
			NSString* propertyName = [NSString stringWithUTF8String: [[property objectForKey: @"name"] bytes]];
			NSString* propertyType = [NSString stringWithUTF8String: [[property objectForKey: @"ptype"] bytes]];
			id propertyValue;
			if ([propertyType isEqualToString: @"NSString"]) propertyValue = [NSString stringWithUTF8String: [[property objectForKey: @"value"] bytes]];
			[object setValue: propertyValue forKey: propertyName];
		}*/
		if (createTransaction == YES) [self.KVStore commitTransaction];
		//NSLog(@"OK: %@ %@ %u", className, object, [object retainCount]);
		return object;
	} @catch (NSException* exception) {
		if (createTransaction == YES) [self.KVStore rollbackTransaction];
		[exception raise];
	}
	return nil;
}

- (id)decodePropertyOfType:(NSString*)type data:(NSData*)data lazyCollections:(BOOL)lazyCollections processedObjects:(NSMutableDictionary*)processedObjects {
	if ([type isEqualToString: @"nil"]) {
		return nil;
	} else if ([type isEqualToString: @"string"]) {
		NSAssert(data != nil, @"UTF-8 string is NULL");
		return [data length] == 0 ? @"" : [[[NSString alloc] initWithBytes: [data bytes] length: [data length] encoding: NSUTF8StringEncoding] autorelease];
		//return [NSString stringWithUTF8String: [data bytes] ?: ""];
	} else if ([type isEqualToString: @"number"]) {
		NSAssert(data != nil, @"UTF-8 string is NULL");
		NSString* string = [data length] == 0 ? @"" : [[[NSString alloc] initWithBytes: [data bytes] length: [data length] encoding: NSUTF8StringEncoding] autorelease];
		//NSString* string = [NSString stringWithUTF8String: [data bytes] ?: ""];
		return [NSNumber numberWithString: string];
		//return [NSNumber numberWithDouble: [string doubleValue]];
	} else if ([type isEqualToString: @"date"]) {
		NSAssert(data != nil, @"UTF-8 string is NULL");
		NSString* string = [data length] == 0 ? @"" : [[[NSString alloc] initWithBytes: [data bytes] length: [data length] encoding: NSUTF8StringEncoding] autorelease];
		//NSString* string = [NSString stringWithUTF8String: [data bytes] ?: ""];
		return [NSDate dateWithTimeIntervalSinceReferenceDate: [string doubleValue]];
	} else if ([type isEqualToString: @"*"]) {
		NSAssert(data != nil, @"UTF-8 string is NULL");
		NSKeyedUnarchiver* unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData: data];
		id object = [[[unarchiver decodeObjectForKey: @"childObject"] retain] autorelease];
		[unarchiver release];
		return object;
	} else if ([type isEqualToString: @"array"] || [type isEqualToString: @"set"]) {
		NSAssert(data != nil, @"UTF-8 string is NULL");
		NSKeyedUnarchiver* unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData: data];
		NSArray* collection = [[unarchiver decodeObjectForKey: @"collection"] retain];
		[unarchiver release];
		id newCollection = [type isEqualToString: @"array"] ? [NSMutableArray arrayWithCapacity: [collection count]] : [NSMutableSet setWithCapacity: [collection count]];
		for (id el in collection) {
			if ([el isKindOfClass: [EXObjectReferenceInCollection class]] == NO) {
				[newCollection addObject: el];
			} else {
				EXObjectReferenceInCollection* objectRef = el;
				NSString* objectID = [objectRef objectID];
				if (lazyCollections == YES) {
					EXObjectProxy* proxy = [[EXObjectProxy alloc] initWithObjectID: objectID objectStore: self];
					[newCollection addObject: proxy];
					[proxy release];
				} else {
					id object = [processedObjects objectForKey: objectID];
					if (object == nil) {
						object = [[self.cache objectForKey: objectID] pointerValue];
						if (object == nil) {
							object = [self objectWithObjectID: objectID lazyCollections: NO createTransaction: NO processedObjects: processedObjects];
						} //else NSLog(@"in cache");
					} //else NSLog(@"in processed objects");
					[newCollection addObject: object];
				}
			}
		}
		[collection release];
		return newCollection;
	} else if ([type isEqualToString: @"dict"]) {
		NSAssert(data != nil, @"UTF-8 string is NULL");
		NSKeyedUnarchiver* unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData: data];
		NSDictionary* dict = [[unarchiver decodeObjectForKey: @"collection"] retain];
		[unarchiver release];
		id newDict = [NSMutableDictionary dictionaryWithCapacity: [dict count]];
		for (id key in [dict allKeys]) {
			id el = [dict objectForKey: key];
			if ([el isKindOfClass: [EXObjectReferenceInCollection class]] == NO) {
				[newDict setObject: el forKey: key];
			} else {
				EXObjectReferenceInCollection* objectRef = el;
				NSString* objectID = [objectRef objectID];
				if (lazyCollections == YES) {
					EXObjectProxy* proxy = [[EXObjectProxy alloc] initWithObjectID: objectID objectStore: self];
					[newDict setObject: proxy forKey: key];
					[proxy release];
				} else {
					id object = [processedObjects objectForKey: objectID];
					if (object == nil) {
						object = [[self.cache objectForKey: objectID] pointerValue];
						if (object == nil) {
							object = [self objectWithObjectID: objectID lazyCollections: NO createTransaction: NO processedObjects: processedObjects];
						} //else NSLog(@"in cache");
					} //else NSLog(@"in processed objects");
					[newDict setObject: object forKey: key];
				}
			}
		}
		[dict release];
		return newDict;
	} else {
		NSAssert(data != nil, @"UTF-8 string is NULL");
		EXObjectReferenceInCollection* objectRef = [[EXObjectReferenceInCollection alloc] initWithData: data];
		if (objectRef == nil) NSLog(@"Nil ref. wrapper for %@ (%u)", type, [data length]);
		NSString* objectID = [objectRef objectID];
		if (objectID == nil) NSLog(@"Nil ref. ID for %@ (%u)", type, [data length]);
		id object = [processedObjects objectForKey: objectID];
		if (object == nil) {
			object = [[self.cache objectForKey: objectID] pointerValue];
			if (object == nil) {
				object = [self objectWithObjectID: objectID lazyCollections: lazyCollections createTransaction: NO processedObjects: processedObjects];
			} //else NSLog(@" %@ in cache", type);
		} //else NSLog(@"%@ in processed objects", type);
		[objectRef release];
		return object;
	}
}

- (NSArray*)objectsOfClass:(Class)cls {
	NSMutableArray* results = [NSMutableArray array];
	//NSAssert(self.currentTransaction == nil, @"Current transaction isn't nil");
	NSMutableDictionary* processedObjects = [[NSMutableDictionary alloc] init];
	[self objectsOfClass: cls createTransaction: YES includeSubclasses: YES results: results processedObjects: processedObjects];
	[processedObjects release];
	return results;
}

- (NSArray*)objectsOfClass:(Class)cls sortedByProperty:(NSString*)propertyName {
	NSMutableArray* results = [NSMutableArray array];
	//NSAssert(self.currentTransaction == nil, @"Current transaction isn't nil");
	NSMutableDictionary* processedObjects = [[NSMutableDictionary alloc] init];
	[self objectsOfClass: cls createTransaction: YES includeSubclasses: YES results: results processedObjects: processedObjects];
	[results sortUsingComparator: ^(id obj1, id obj2) {
		return [[obj1 valueForKey: propertyName] compare: [obj2 valueForKey: propertyName]];
	}];
	[processedObjects release];
	return results;
}

- (EXPredicate*)predicateWithClass:(Class)cls {
	return [EXPredicate predicateWithClass: cls objectStore: self];
}

- (EXPredicate*)predicateWithClass:(Class)cls predicate:(NSPredicate*)_predicate {
	EXPredicate* predicate = [self predicateWithClass: cls];
	predicate.filterBlock = ^BOOL(id object) {
		return [_predicate evaluateWithObject: object];
	};
	return predicate;
}

- (NSArray*)objectsWithPredicate:(EXPredicate*)predicate {
	return [self objectsWithPredicate: predicate createTransaction: YES processedObjects: nil];
}

- (NSArray*)objectsWithPredicate:(EXPredicate*)predicate createTransaction:(BOOL)createTransaction processedObjects:(NSMutableDictionary*)_processedObjects {
	//NSAssert(self.currentTransaction == nil, @"Current transaction isn't nil");
	NSMutableDictionary* processedObjects = _processedObjects != nil ? [_processedObjects retain] : [[NSMutableDictionary alloc] init];
	@try {
		if (predicate.filterBlock == nil) {
			NSMutableArray* results = [NSMutableArray array];
			[self objectsOfClass: [predicate predicateClass] createTransaction: createTransaction includeSubclasses: YES results: results condBlock: [predicate condBlock] processedObjects: processedObjects];
			return results;
		} else {
			NSMutableArray* results = [[NSMutableArray alloc] init];
			[self objectsOfClass: [predicate predicateClass] createTransaction: createTransaction includeSubclasses: YES results: results condBlock: nil processedObjects: processedObjects];
			NSMutableArray* filteredResults = [NSMutableArray array];
			for (id object in results) {
				if (predicate.filterBlock(object) == YES) {
					[filteredResults addObject: object];
				}
			}
			[results release];
			return filteredResults;
		}
	} @finally {
		[processedObjects release];
	}
	return nil;
}

- (NSArray*)objectsOfClass:(Class)cls filter:(EXQuery*)query {
	return [self objectsOfClass: cls filter: query createTransaction: YES];
}

- (NSArray*)objectsOfClass:(Class)cls filter:(EXQuery*)query createTransaction:(BOOL)createTransaction {
	//NSAssert(self.currentTransaction == nil, @"Current transaction isn't nil");
	NSMutableDictionary* processedObjects = [[NSMutableDictionary alloc] init];
	@try {
		NSString* predicateFormat = [[query predicate] predicateFormat];
		if (predicateFormat == nil) predicateFormat = @"";
		id _condBlock = [queryBlocks objectForKey: predicateFormat];
		void(^condBlock)(void*) = _condBlock == [NSNull null] ? nil : _condBlock;
		if (_condBlock == nil) {
			NSLog(@"Optimized queries not supported!"); exit(1);
			EXQueryOptimizer* queryOptimizer = [[EXQueryOptimizer alloc] initWithQuery: query];
			condBlock = [queryOptimizer conditionalBlockWithKVStore: self.KVStore];
			[queryBlocks setObject: (condBlock != nil ? (id) condBlock : [NSNull null]) forKey: predicateFormat];
			[queryOptimizer release];
		}
		if (condBlock == nil) {
			//NSLog(@"Unoptimized query (using block)");
			NSMutableArray* results = [[NSMutableArray alloc] init];
			[self objectsOfClass: cls createTransaction: createTransaction includeSubclasses: YES results: results processedObjects: processedObjects];
			NSMutableArray* results2 = [NSMutableArray array];
			for (id object in results) {
				if ([query block](object) == YES) [results2 addObject: object];
			}
			[results release];
			return results2;
		} else {
			//NSLog(@"Optimized query");
			NSMutableArray* results = [[[NSMutableArray alloc] init] autorelease];
			[self objectsOfClass: cls createTransaction: createTransaction includeSubclasses: YES results: results condBlock: condBlock processedObjects: processedObjects];
			return results;
		}
	} @finally {
		[processedObjects release];
	}
	return nil;
}

- (void)objectsOfClass:(Class)cls createTransaction:(BOOL)createTransaction includeSubclasses:(BOOL)includeSubclasses results:(NSMutableArray*)results processedObjects:(NSMutableDictionary*)processedObjects {
	[self objectsOfClass: cls createTransaction: createTransaction includeSubclasses: includeSubclasses results: results condBlock: nil processedObjects: processedObjects];
}

- (void)objectsOfClass:(Class)cls createTransaction:(BOOL)createTransaction includeSubclasses:(BOOL)includeSubclasses results:(NSMutableArray*)results condBlock:(void(^)(void*))condBlock processedObjects:(NSMutableDictionary*)processedObjects {
	if (createTransaction == YES && [self.KVStore beginTransaction] == NO) return;
	@try {
		NSString* className = NSStringFromClass(cls);
		//NSLog(@"getting object of class %@", className);
		NSArray* metadata; NSString* keyAttribute;
		metadata = [self.KVStore getMapsWithCriteria: [NSDictionary dictionaryWithObjectsAndKeys: @"object", @"type", className, @"class", nil]
									  condBlock: condBlock];
		keyAttribute = @"__key";
		/*if (condBlock == nil) {
			metadata = [KVStore getMapsWithCriteria: [NSDictionary dictionaryWithObjectsAndKeys: @"object", @"type", className, @"class", nil]];
			keyAttribute = @"__key";
		} else {
			metadata = [KVStore getMapsWithCriteria: [NSDictionary dictionaryWithObjectsAndKeys: @"property", @"type", className, @"class", nil]
										  condBlock: condBlock];
			keyAttribute = @"objectID";
		}*/
		for (NSDictionary* dict in metadata) {
			id objectID = [dict objectForKey: keyAttribute];
			if ([objectID isKindOfClass: [NSData class]]) {
				NSAssert(objectID != nil, @"UTF-8 string is NULL");
				objectID = [[[NSString alloc] initWithBytes: [objectID bytes] length: [objectID length] encoding: NSUTF8StringEncoding] autorelease];
				//objectID = [NSString stringWithUTF8String: [objectID bytes]];
			}
			id object = [[self.cache objectForKey: objectID] pointerValue];
			BOOL foundInCache = YES;
			if (object == nil) {
				foundInCache = NO;
				object = [[cls alloc] init];
				[object __setObjectStore: self];
				NSAssert([self.cache objectForKey: objectID] == nil, @"Object already in cache");
				NSValue* pointerKey = [NSValue valueWithPointer: object];
				[self.cache setObject: pointerKey forKey: objectID];
				NSAssert([processedObjects objectForKey: objectID] == nil, @"Object is in processed objects");
				[processedObjects setObject: object forKey: objectID];
				NSAssert([[self.mapOfIDs allValues] containsObject: objectID] == NO, @"Object already in map of IDs");
				//if ([[self.mapOfIDs allValues] containsObject: objectID]) NSLog(@"Warning: replacing a re-bound object");
				[self.mapOfIDs setObject: objectID forKey: pointerKey];
				[object release];
			} //else NSLog(@"Object taken from cache");
			[results addObject: object];
			//NSLog(@"### %@ %u", [object className], [object retainCount]);
			if (foundInCache == NO) {
			NSArray* fieldNames = [[object class] ORMPersistentFieldNames];
				//for (NSString* attr in dict) {
				for (NSString* propertyName in fieldNames) {
					if ([[[object class] ORMTransientFieldNames] containsObject: propertyName]) continue;
					//if ([attr rangeOfString: @"field_"].location == 0) {
					NSString* attr = [NSString stringWithFormat: @"field_%@", propertyName];
					NSData* data = [dict objectForKey: attr];
					if (data != nil) {
						//NSString* propertyName = [attr substringFromIndex: 6];
						//NSLog(@"3: %@ %@", propertyName, attr);
						NSData* typeData = [dict objectForKey: [NSString stringWithFormat: @"type_%@", propertyName]];
						NSAssert(typeData != nil, @"Type data is nil -- database probably corrupteed");
						NSString* propertyType = [[NSString alloc] initWithBytes: [typeData bytes] length: [typeData length] encoding: NSUTF8StringEncoding];
						//NSString* propertyType = [NSString stringWithUTF8String: [typeData bytes]];
						id propertyValue = [self decodePropertyOfType: propertyType data: data lazyCollections: NO processedObjects: processedObjects];
						[propertyType release]; //
						/*if ([fieldNames containsObject: propertyName])*/ [object setValue: propertyValue forKey: propertyName];
					}
				}
			}
			if ([object respondsToSelector: @selector(awakeFromStore:)]) [object performSelector: @selector(awakeFromStore:) withObject: self];
			/*NSArray* properties = [KVStore getMapsWithCriteria: [NSDictionary dictionaryWithObjectsAndKeys: @"property", @"type", objectID, @"objectID", nil]];
			for (NSDictionary* property in properties) {
				NSString* propertyName = [NSString stringWithUTF8String: [[property objectForKey: @"name"] bytes]];
				NSString* propertyType = [NSString stringWithUTF8String: [[property objectForKey: @"ptype"] bytes]];
				id propertyValue;
				if ([propertyType isEqualToString: @"NSString"]) propertyValue = [NSString stringWithUTF8String: [[property objectForKey: @"value"] bytes]];
				[object setValue: propertyValue forKey: propertyName];
			}*/
		}
		if (includeSubclasses == YES) {
			NSSet* subclasses = [EXObjectStore subclassesOfClass: cls];
			//NSLog(@"subclasses: %@", subclasses);
			for (NSString* subclassName in subclasses) {
				Class subclass = NSClassFromString(subclassName);
				[self objectsOfClass: subclass createTransaction: NO includeSubclasses: NO results: results condBlock: condBlock processedObjects: processedObjects];
			}
		}
		if (createTransaction == YES) [self.KVStore commitTransaction];
	} @catch (NSException* exception) {
		if (createTransaction == YES) [self.KVStore rollbackTransaction];
		[exception raise];
	}
}

+ (NSSet*)subclassesOfClass:(Class)cls {
	NSString* className = NSStringFromClass(cls);
	NSMutableSet* subclasses = [subclassMap objectForKey: className];
	if (subclasses == nil) {
		if ([allClasses count] == 0) {
			int clsnum = objc_getClassList(NULL, 0);
			//NSLog(@"# %d", clsnum);
			Class* classes = malloc(sizeof(Class) * clsnum);
			objc_getClassList(classes, clsnum);
			for (int i = 0; i < clsnum; i++) {
				Class subclass = classes[i];
				NSString* subclassName = NSStringFromClass(subclass);
				//NSLog(@"%@", subclassName);
				if ([subclassName characterAtIndex: 0] != '_'
					&& [subclassName isEqualToString: @"NSLeafProxy"] == NO				// because of OS X 10.5
					&& [subclassName isEqualToString: @"List"] == NO					// because of OS X 10.5
					&& [subclassName isEqualToString: @"NSATSGlyphGenerator"] == NO		// because of OS X 10.5
					&& [subclassName isEqualToString: @"NSMessageBuilder"] == NO
					&& [subclassName isEqualToString: @"Object"] == NO
					&& [subclassName isEqualToString: @"Protocol"] == NO
					) {
					[allClasses addObject: subclassName];
				}
			}
			free(classes);
		}
		subclasses = [[NSMutableSet alloc] init];
		for (NSString* subclassName in allClasses) {
			//NSLog(@"%@", subclassName);
			Class subclass = NSClassFromString(subclassName);
			if (subclass != cls && [subclass isSubclassOfClass: cls]) [subclasses addObject: subclassName];
		}
		[subclassMap setObject: subclasses forKey: className];
		[subclasses release];
	}
	return subclasses;
}

- (id)singletonOfClass:(Class)cls createIfNotFound:(BOOL)createIfNotFound {
	NSArray* objects = [self objectsOfClass: cls];
	if ([objects count] == 0) {
		if (createIfNotFound == YES) {
			id object = [[[cls alloc] init] autorelease];
			if ([object respondsToSelector: @selector(awakeFromStore:)]) [object performSelector: @selector(awakeFromStore:) withObject: self];
			[self storeObject: object];
			return object;
		} else return nil;
	}
	if ([objects count] > 1) NSLog(@"Warning: singleton (%@) has more than one instance (%u)", NSStringFromClass(cls), [objects count]);
	return [objects lastObject];
}

- (EXTransaction*)beginTransaction {
	[self.KVStore beginTransaction];
	NSAssert(self.currentTransaction == nil, @"Can't begin transaction, another one in progress");
	EXTransaction* transaction = [[EXTransaction alloc] initWithObjectStore: self];
	self.currentTransaction = transaction;
	[transaction release];
	if ([delegate respondsToSelector: @selector(didBeginTransaction:)])
		[delegate performSelector: @selector(didBeginTransaction:) withObject: self];
	return transaction;
}

/*- (NSObject<ORMKeyValueStore>*)KVStore {
	return KVStore;
}*/

- (EXObjectStore<EXObjectStoreExt>*)ext {
	return (EXObjectStore<EXObjectStoreExt>*) self;
}

- (NSString*)versionString {
	return @"2.0";
}

- (NSString*)uniqueKey {
	return [KVStore uniqueKey];
}

- (void)close {
	[KVStore close];
	[KVStore release];
	KVStore = nil;
}

- (void)dealloc {
	for (id pointerValue in [cache allValues]) {
		id object = [pointerValue pointerValue];
		[object __setObjectStore: nil];
	}
	[self close];
	[mapOfIDs release];
	[cache release];
	[delegate release];
	//[zombies release];
	[super dealloc];
}

- (void)finalize {
	[self close];
	[super finalize];
}

@end
